﻿Imports Microsoft.IdentityModel.Protocols.WSFederation.Metadata
Imports Microsoft.IdentityModel.SecurityTokenService
Imports System
Imports System.Collections.Generic
Imports System.IdentityModel.Tokens
Imports System.IO
Imports System.Linq
Imports System.Security.Cryptography.X509Certificates
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Description
Imports System.Text
Imports System.Xml
Imports Microsoft.IdentityModel.Protocols.WSIdentity

Public Class MetadataGenerator
    ' Methods
    Public Sub New(ByVal contact As ContactPerson, ByVal organization As Organization, ByVal issuerUri As String, ByVal serviceName As String, ByVal activeEndpoints As Collections.Specialized.StringCollection, ByVal passiveEndpoints As Collections.Specialized.StringCollection, ByVal claims As CustomDisplayClaimCollection, ByVal serviceCert As X509Certificate2, ByVal sign As Boolean)
        Me.contact = contact
        Me.organization = organization
        Me.issuerUri = issuerUri
        Me.serviceName = serviceName
        Me.activeEndpoints = activeEndpoints
        Me.passiveEndpoints = passiveEndpoints
        Me.claims = claims
        Me.serviceCert = serviceCert
        Me.sign = sign
    End Sub

    Private Function CreateFederationMetadataAsString(ByVal entity As EntityDescriptor) As String
        Dim serializer As New MetadataSerializer
        Dim stream As New MemoryStream
        serializer.WriteMetadata(stream, entity)
        stream.Seek(0, SeekOrigin.Begin)
        Return Encoding.UTF8.GetString(stream.ToArray)
    End Function

    Public Sub GenerateAsFile(ByVal fileName As String)
        Dim contents As String = Me.GenerateCore
        File.WriteAllText(fileName, contents)
    End Sub

    Public Function GenerateAsString() As String
        Return Me.GenerateCore
    End Function

    Private Function GenerateCore() As String
        Dim tokenServiceDescriptor As SecurityTokenServiceDescriptor = Me.GetTokenServiceDescriptor
        Dim entityId As New EntityId(Me.issuerUri)
        Dim entity As New EntityDescriptor(entityId)
        If Me.sign Then
            entity.SigningCredentials = New X509SigningCredentials(Me.serviceCert)
        End If
        entity.Contacts.Add(Me.contact)
        entity.Organization = Me.organization
        entity.RoleDescriptors.Add(tokenServiceDescriptor)
        Return Me.CreateFederationMetadataAsString(entity)
    End Function

    Private Function GetSingingKeyDescriptor(ByVal cert As X509Certificate2) As KeyDescriptor
        Dim clause As X509RawDataKeyIdentifierClause = New X509SecurityToken(cert).CreateKeyIdentifierClause(Of X509RawDataKeyIdentifierClause)()
        Dim descriptor As New KeyDescriptor(New SecurityKeyIdentifier(New SecurityKeyIdentifierClause() {clause}))
        descriptor.Use = KeyType.Signing
        Return descriptor
    End Function

    Private Function GetTokenServiceDescriptor() As SecurityTokenServiceDescriptor
        Dim action As Action(Of EndpointAddress) = Nothing
        Dim tokenService As New SecurityTokenServiceDescriptor
        tokenService.ServiceDescription = Me.serviceName
        tokenService.Keys.Add(Me.GetSingingKeyDescriptor(Me.serviceCert))
        tokenService.ProtocolsSupported.Add(New Uri("http://docs.oasis-open.org/wsfed/federation/200706"))
        tokenService.TokenTypesOffered.Add(New Uri("urn:oasis:names:tc:SAML:1.0:assertion"))
        For Each claim In claims
            tokenService.ClaimTypesOffered.Add(claim.GetDiplayClaim)
        Next
        Dim uri As String
        For Each uri In Me.activeEndpoints
            Dim [set] As New MetadataSet
            Dim metadata As New MetadataReference(New EndpointAddress(String.Format("{0}/mex", Uri)), AddressingVersion.WSAddressing10)
            Dim item As New MetadataSection(MetadataSection.MetadataExchangeDialect, Nothing, metadata)
            [set].MetadataSections.Add(item)
            Dim sb As New StringBuilder
            Dim w As New StringWriter(sb)
            Dim writer As New XmlTextWriter(w)
            [set].WriteTo(writer)
            writer.Flush()
            w.Flush()
            Dim input As New StringReader(sb.ToString)
            Dim reader As New XmlTextReader(Input)
            Dim metadataReader As XmlDictionaryReader = XmlDictionaryReader.CreateDictionaryReader(reader)
            Dim address As New EndpointAddress(New Uri(Uri), Nothing, Nothing, metadataReader, Nothing)
            tokenService.SecurityTokenServiceEndpoints.Add(address)
        Next
        Dim addPassiveEndpointsAsActive As Boolean = tokenService.SecurityTokenServiceEndpoints.Count = 0
        For Each item In Me.passiveEndpoints
            Dim endpoint As New EndpointAddress(item)
            tokenService.PassiveRequestorEndpoints.Add(endpoint)
            If addPassiveEndpointsAsActive Then
                tokenService.SecurityTokenServiceEndpoints.Add(endpoint)
            End If
        Next
        Return tokenService
    End Function


    ' Fields
    Private activeEndpoints As Collections.Specialized.StringCollection
    Private claims As CustomDisplayClaimCollection
    Private contact As ContactPerson
    Private issuerUri As String
    Private organization As Organization
    Private passiveEndpoints As Collections.Specialized.StringCollection
    Private serviceCert As X509Certificate2
    Private serviceName As String
    Private sign As Boolean
End Class